#!/usr/bin/python
# -*- coding: utf-8 -*-

u"""
TODO: docstring
"""

__version__ = '$Id: pygletbase.py 264 2009-04-25 20:45:34Z dr0iddr0id $'


import pyglet

# local imports
from bipy import gamebase
from bipy.events import Event

#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
class Redirector(object):

    class _TypeRedirect(object):

        def __init__(self, win, a_event_type, a_other_handler):
            self._type = a_event_type
            self._other_handler = a_other_handler
            self._win = win

        def __call__(self, *args, **kwargs):
            return self._other_handler(self._win, self._type, *args, **kwargs)

    def __init__(self, win, event_types, handler):
        self.update(win, event_types)
        self._handler = handler

    def handle(self, win, event_type, *args, **kwargs):
        return self._handler(win, event_type, *args, **kwargs)

    def update(self, win, event_types):
        for event_type in event_types:
            if hasattr(self, event_type):
                # should not happen that this instance has an attribut named 
                # like the event_type, if it does it has to be an instance of
                # self._TypeRedirect otherwise it is an error and you need to 
                # rename either event or the attribute
                # replace the old _TypeRedirector with new one
                assert(isinstance(getattr(self, event_type), self._TypeRedirect))
            setattr(self, event_type, self._TypeRedirect(win, event_type, self.handle))
#-------------------------------------------------------------------------------
#class RedirectedWindow(pyglet.window.Window):
#
#    def __init__(self, *args, **kwargs):
#        super(RedirectedWindow, self).__init__(*args, **kwargs)
#        # XXX: make redirected event_types configurable?
#        r = Redirector(self, list(self.event_types), self.on_redirected_event)
#        self.push_handlers(r)
#
#    def on_redirected_event(self, source_win, event_type, *args, **kwargs):
#        print 'on_redirected_event', source_win, event_type, args, kwargs
#        pass
#        #return EVENT_HANDLED

#-------------------------------------------------------------------------------
class PygletGameBase(gamebase.GameBase):
    u"""
    # TODO: docstring
    """

    def __init__(self, first_state, state_args=[], 
                 state_kwargs={}, 
                 size=(800, 600), 
                 flags=None, 
                 caption=None, 
                 resizable=False, 
                 style=None, 
                 fullscreen=False, 
                 visible=True, 
                 vsync=True, 
                 display=None, 
                 screen=None, 
                 config=None, 
                 context=None, 
                 *args, 
                 **kwargs):
        u"""
        Same arguments as GameBase.
        Additionally: size and flags are the arugments for pygame.display.set_mode().
        Access to the surface of the screen is given by the attribute 'screen'.
        """
        #TODO: update doc
        #TODO: maybe screen should be probagated with on_render!?        
        super(PygletGameBase, self).__init__(first_state, state_args, state_kwargs, *args, **kwargs)
        self.screen = pyglet.window.Window(width = size[0], 
                                           height = size[1], 
                                           caption=caption, 
                                           resizable=resizable, 
                                           style=style, 
                                           fullscreen=fullscreen, 
                                           visible=visible, 
                                           vsync=vsync, 
                                           display=display, 
                                           screen=screen, 
                                           config=config, 
                                           context=context)
        self._redirector = Redirector(self.screen, list(self.screen.event_types), self._handle_events)
        self.screen.push_handlers(self._redirector)

        self._time = 0

    def _handle_events(self, source_win, event_type, *args, **kwargs):
        print source_win, event_type, args, kwargs
        #print self.screen.event_types
        # currently registered event_type 

        #-- mouse --#
        if event_type == 'on_mouse_motion':
            self._cur_state.event_mouse_motion(*args, **kwargs)
        elif event_type == 'on_mouse_drag':
            self._cur_state.event_mouse_drag(*args, **kwargs)
        elif event_type == 'on_mouse_press':
            self._cur_state.event_mouse_button_down(*args, **kwargs)
        elif event_type == 'on_mouse_release':
            self._cur_state.event_mouse_button_up(*args, **kwargs)
        elif event_type == 'on_mouse_scroll':
            self._cur_state.event_mouse_scroll(*args, **kwargs)
        elif event_type == 'on_mouse_enter':
            self._cur_state.event_mouse_enter(*args, **kwargs)
        elif event_type == 'on_mouse_leave':
            self._cur_state.event_mouse_leave(*args, **kwargs)
        #-- keyboard --#
        elif event_type == 'on_key_press':
            self._cur_state.event_key_down(*args, **kwargs)
        elif event_type == 'on_key_release':
            self._cur_state.event_key_up(*args, **kwargs)
        #-- text --#
        elif event_type == 'on_text':
            self._cur_state.event_text(*args, **kwargs)
        elif event_type == 'on_text_motion':
            self._cur_state.event_text_motion(*args, **kwargs)
        elif event_type == 'on_text_motion_select':
            self._cur_state.event_text_motion_select(*args, **kwargs)
        #-- window --#
        elif event_type == 'on_move':
            self._cur_state.event_move(*args, **kwargs)
        elif event_type == 'on_activate':
            self._cur_state.event_activate(*args, **kwargs)
        elif event_type == 'on_deactivate':
            self._cur_state.event_deactivate(*args, **kwargs)
        elif event_type == 'on_show':
            self._cur_state.event_show(*args, **kwargs)
        elif event_type == 'on_hide':
            self._cur_state.event_hide(*args, **kwargs)
        #-- context --#
        elif event_type == 'on_context_lost':
            self._cur_state.event_context_lost(*args, **kwargs)
        elif event_type == 'on_context_state_lost':
            self._cur_state.event_context_state_lost(*args, **kwargs)
#        elif event_type == 'on_draw':
#            self._cur_state.event_ (*args, **kwargs)
        #-- quit --#
        elif event_type == 'on_close':
            self._cur_state.event_quit(self, *args, **kwargs)
        #-- video --#
        elif event_type == 'on_expose':
            self._cur_state.event_expose(*args, **kwargs)
        elif event_type == 'on_resize':
            self._cur_state.event_resize(*args, **kwargs)

        # prevent further propagation
        return pyglet.event.EVENT_HANDLED

    #- override -#
    def _schedule_once(self, func, dt):
        u"""
        Internal use. pyglet specific implementation for the scheduler interface.
        """
        pyglet.clock.schedule_once(dt/1000., func)

    #- override -#
    def _unschedule(self, func):
        u"""
        Internal use. pyglet specific implementation for the scheduler interface.
        """
        pyglet.clock.unschedule(func)

    #- override -#
    def _get_time(self):
        u"""
        Return time since start [ms].
        """
        dt = pyglet.clock.tick() * 1000.0
        self._time += dt
        return self._time

    #- override -#
    def _on_framerate_changed(self, tpf):
        u"""
        Internal use. Adjusting the fps of pyglet.
        """
        print '?'*20,  tpf
        if 0 < tpf:
            pyglet.clock.schedule_interval(self._pre_update, tpf / 1000.0)
        else:
            # blocking mode
            pyglet.clock.unschedule(self._pre_update)
            pass

    def _pre_update(self, dt):
        dt *= 1000.0
        self._time += dt
        self._update(dt,  self._time)

    #- override -#
    def run(self):
        u"""
        Start the game. pyglet specific main loop.
        """
        self._update = self._first_update
        pyglet.app.run()

    #- override -#
    def quit(self):
        u"""
        call this to stop the game from running
        """
        print 'pygletbase quit called !!!!!!!!!!!!!!!!!'
        self._update = self._last_update
        self.screen.has_exit = True

    #- override -#
    def _last_update(self, dt, t):
        u"""
        Last call to update will execute this function . Internal use only.
        """
        super(PygletGameBase, self)._last_update(dt, t)
        print '>'*20, 'going to exit app!!'
        pyglet.app.exit()

#-------------------------------------------------------------------------------


#  	on_activate() The window was activated.
#  	on_close()  The user attempted to close the window.
#  	on_context_lost()  The window's GL context was lost.
#  	on_context_state_lost()  The state of the window's GL context was lost.
#  	on_deactivate()  The window was deactivated.
#  	on_draw()  The window contents must be redrawn.
#  	on_expose()  A portion of the window needs to be redrawn.
#  	on_hide()  The window was hidden.
#  	on_key_press(symbol, modifiers)  A key on the keyboard was pressed (and held down).
#  	on_key_release(symbol, modifiers) A key on the keyboard was released.
#  	on_mouse_drag(x, y, dx, dy, buttons, modifiers)  The mouse was moved with one or more mouse buttons pressed.
#  	on_mouse_enter(x, y)  The mouse was moved into the window.
#  	on_mouse_leave(x, y)  The mouse was moved outside of the window.
#  	on_mouse_motion(x, y, dx, dy)  The mouse was moved with no buttons held down.
#  	on_mouse_press(x, y, button, modifiers)  A mouse button was pressed (and held down).
#  	on_mouse_release(x, y, button, modifiers)  A mouse button was released.
#  	on_mouse_scroll(x, y, scroll_x, scroll_y)  The mouse wheel was scrolled.
#  	on_move(x, y)  The window was moved.
#  	on_resize(width, height)  The window was resized.
#  	on_show()  The window was shown.
#  	on_text(text)  The user input some text.
#  	on_text_motion(motion)  The user moved the text input cursor.
#  	on_text_motion_select(motion)  The user moved the text input cursor while extending the selection.

class PygletBaseState(gamebase.GameState):
    u"""
    Base state for pyglet base games. Add pyglet specifig events.
    For the other events see gamebase.GameState.
    
    pyglet specific events::
    
        self.event_context_lost     on_context_lost()
        self.event_context_state_lost on_context_state_lost()
        self.event_draw             on_draw(dt)
        self.event_hide             on_hide()
        self.event_mouse_scroll     on_mouse_scroll(x, y, scroll_x, scroll_y)
        self.event_move             on_move(x, y)
        self.event_show             on_show()
        self.event_text             on_text(text)
        self.event_text_motion      on_text_motion(motion)
        self.event_text_motion_select on_text_motion_select(motion)
    
    """
    def __init__(self, *args, **kwargs):
        u"""
        State init.
        """
        super(PygletBaseState, self).__init__(*args, **kwargs)

        #-- pyglet events --#
        self.event_context_lost = Event(u'event_context_lost')       # the window's GL context was lost
        self.event_context_state_lost = Event(u'event_context_state_lost') # the state of the window's GL context was lost
        self.event_draw = Event(u'event_draw')               
        self.event_hide = Event(u'event_hide')               # window was hidden
        self.event_mouse_scroll = Event(u'event_mouse_scroll')  # x, y, scroll_x, scroll_y
        self.event_move = Event(u'event_move') # x, y The window was moved
        self.event_show = Event(u'event_show') # The window was shown
        self.event_text = Event(u'event_text') # The user input some text
        self.event_text_motion = Event(u'event_text_motion') # The user moved the text input cursor
        self.event_text_motion_select = Event(u'event_text_motion_select') # The user moved the text input cursor while extending the selection

#-------------------------------------------------------------------------------

def main():
    game = PygletGameBase(PygletBaseState())
    #-- blocking: this is needed if you only want to react on events --#
    game.framerate = 0
    def on_close(g):
        g.quit()
        g._update(1, 1)
    game.get_game_state().event_close += on_close
    #-- blocking --#
    # GameTime scheduler does not work in blocking mode
    def update(gdt, gt, dt, t):
        print 'time update', gdt, gt, dt, t
#    game.get_game_state().game_time.event_update += update
#    def cb(i, j):
#        i+j
#        #print 'cb called %i times and %i repeats' % (i, j)
#    for i in range(100):
#        for j in range(50):
#            game.get_game_state().game_time.schedule(i*100, cb, i, j)
    game.run()

if __name__ == "__main__":
    import sys
    import traceback
    if "profile" in sys.argv:
        import hotshot
        import hotshot.stats
        import tempfile
        import os
 
        profile_data_fname = tempfile.mktemp("prf")
        try:
            prof = hotshot.Profile(profile_data_fname)
            prof.run('main()')
            del prof
            s = hotshot.stats.load(profile_data_fname)
#            s.strip_dirs()
            sys.stdout.write(u"cumulative %s%s" %(os.linesep, os.linesep))
            s.sort_stats('cumulative').print_stats()
            sys.stdout.write(u"By time. %s%s" %(os.linesep, os.linesep))
            s.sort_stats('time').print_stats()
            del s
        finally:
            # clean up the temporary file name.
            try:
                os.remove(profile_data_fname)
            except:
                # may have trouble deleting ;)
                pass
    else:
        try:
            main()
        except:
            traceback.print_exc(sys.stderr)
